package db

import (
	"context"
	"fmt"
	"log"

	"commonUtils/config"

	"github.com/qiniu/qmgo"
	"github.com/qiniu/qmgo/operator"
	"github.com/qiniu/qmgo/options"
	"go.mongodb.org/mongo-driver/bson"
)

const (
	// PK 主键键名
	PK = "_id"
)

type MongoClient struct {
	dbname string
	Ctx    context.Context
	Sess   *qmgo.Client
	Db     *qmgo.Database
	ConStr string
}

func NewMongoClient(conf config.NoSQLDBConfig) *MongoClient {
	var conStr = "mongodb://"
	var dbname = ""
	if len(conf.User) > 0 {
		conStr += conf.User + ":" + conf.Pass + "@"
	}
	conStr += conf.Addr + ":" + conf.Port
	fmt.Println("connect mongo database : " + conStr)
	ctx := context.Background()
	client, err := qmgo.NewClient(ctx, &qmgo.Config{Uri: conStr})
	if err != nil {
		log.Fatal(err)
	}
	dbname = conf.DBName
	db := client.Database(dbname)
	return &MongoClient{dbname, ctx, client, db, conStr}
}

func (c *MongoClient) ReConnect() {
	c.Sess.Close(c.Ctx)
	ctx := context.Background()
	client, err := qmgo.NewClient(ctx, &qmgo.Config{Uri: c.ConStr})
	if err != nil {
		log.Fatal(err)
	}
	c.Sess = client
	c.Db = client.Database(c.dbname)
	c.Ctx = ctx
}

func (c *MongoClient) GetChashbinConnStr() string {
	return c.ConStr + "/" + c.dbname + "_cashbin"
}

// -------------查询数据相关----------------
func (c *MongoClient) FindAndSort(tablename string, query bson.M, sort []string, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).Sort(sort...).All(out)
}

func (c *MongoClient) Find(tablename string, query bson.M, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).All(out)
}

func (c *MongoClient) FindOne(tablename string, query bson.M, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).One(out)
}
func (c *MongoClient) FindOneBySort(tablename string, query bson.M, sort []string, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).Sort(sort...).One(out)
}

// sort ["+a","-b"]
func (c *MongoClient) Page(tablename string, query bson.M, sort []string, skip int, limit int, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).Sort(sort...).Skip(int64(skip)).Limit(int64(limit)).All(out)
}

func (c *MongoClient) Count(tablename string, query bson.M) (int64, error) {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).Count()
}

func (c *MongoClient) Distinct(tablename string, key string, query bson.M, out interface{}) error {
	table := c.Db.Collection(tablename)
	return table.Find(context.Background(), query).Distinct(key, out)
}

func (c *MongoClient) GetByID(tablename string, id interface{}, out interface{}) error {
	return c.FindOne(tablename, bson.M{"_id": id}, out)
}

func (c *MongoClient) Indexes(tablename string, out interface{}) error {
	table := c.Db.Collection(tablename)
	cl, err := table.CloneCollection()
	if err != nil {
		return err
	}
	ctx := context.Background()
	indexes := cl.Indexes()
	cur, err := indexes.List(ctx)
	if err != nil {
		return err
	}
	return cur.All(ctx, out)
}

// -----------------------处理数据相关--------------------------
func (c *MongoClient) SetIndex(tablename string, name string, keys []string) error {
	table := c.Db.Collection(tablename)
	model := options.IndexModel{Key: keys}
	model.SetName(name)
	return table.CreateOneIndex(context.Background(), model)
}

func (c *MongoClient) DropIndex(tablename string, names []string) error {
	table := c.Db.Collection(tablename)
	return table.DropIndex(context.Background(), names)
}

// Insert
func (c *MongoClient) Insert(tablename string, data bson.M) (interface{}, error) {
	return c.CtxInsert(tablename, context.Background(), data)
}

func (c *MongoClient) CtxInsert(tablename string, ctx context.Context, data bson.M) (interface{}, error) {

	table := c.Db.Collection(tablename)
	result, err := table.InsertOne(ctx, data)
	if err != nil {
		return nil, err
	}
	return result.InsertedID, nil
}

// Upsert
func (c *MongoClient) Upsert(tablename string, id interface{}, data bson.M) error {
	return c.CtxUpsert(tablename, context.Background(), id, data)
}

func (c *MongoClient) CtxUpsert(tablename string, ctx context.Context, id interface{}, data bson.M) error {

	table := c.Db.Collection(tablename)
	return table.UpdateId(ctx, id, data)
}

// Delete
func (c *MongoClient) Delete(tablename string, query bson.M) (int64, error) {
	return c.CtxDelete(tablename, context.Background(), query)
}

func (c *MongoClient) CtxDelete(tablename string, ctx context.Context, query bson.M) (int64, error) {

	table := c.Db.Collection(tablename)
	result, err := table.RemoveAll(ctx, query)
	return result.DeletedCount, err
}

// Update
func (c *MongoClient) Update(tablename string, query bson.M, data bson.M) (int64, error) {
	return c.CtxUpdate(tablename, context.Background(), query, data)
}

func (c *MongoClient) CtxUpdate(tablename string, ctx context.Context, query bson.M, data bson.M) (int64, error) {

	table := c.Db.Collection(tablename)
	result, err := table.UpdateAll(ctx, query, data)
	if err != nil {
		return 0, err
	}
	return result.ModifiedCount, nil
}

// UpdateID
func (c *MongoClient) UpdateID(tablename string, id interface{}, data bson.M) error {
	return c.CtxUpdateID(tablename, context.Background(), id, data)
}

func (c *MongoClient) CtxUpdateID(tablename string, ctx context.Context, id interface{}, data bson.M) error {
	table := c.Db.Collection(tablename)
	return table.UpdateId(ctx, id, data)
}

// DeleteID
func (c *MongoClient) DeleteID(tablename string, id interface{}) error {
	return c.CtxDeleteID(tablename, context.Background(), id)
}

func (c *MongoClient) CtxDeleteID(tablename string, ctx context.Context, id interface{}) error {
	table := c.Db.Collection(tablename)
	return table.RemoveId(ctx, id)
}

// --------------------------------- 高级应用 ----------------------------------------------
// 预定义操作符
func (c *MongoClient) Aggregate(tablename string, options []bson.D, opt []options.AggregateOptions, out interface{}) error {
	return c.Db.Collection(tablename).Aggregate(context.Background(), options, opt...).All(out)
}

// 联合查询
func (c *MongoClient) Lookup(tablename, foreignTablename string, key, foreignKey string, asKey string, match bson.M, out interface{}) error {
	return c.Aggregate(tablename, []bson.D{{bson.E{Key: operator.Lookup, Value: bson.M{
		"from":         foreignTablename,
		"localField":   key,
		"foreignField": foreignKey,
		"as":           asKey,
	}}}, {bson.E{Key: operator.Match, Value: match}}}, nil, out)
}

// Group 统计
func (c *MongoClient) Group(tablename string, key string, group, match bson.M, out interface{}) error {
	return c.Aggregate(tablename, []bson.D{{{Key: operator.Match, Value: match}}, {{Key: operator.Group, Value: group}}}, nil, out)
}

// 事务 call内调用的更新
func (c *MongoClient) Transaction(tablename string, call func(context.Context) (interface{}, error)) (interface{}, error) {
	ctx := context.Background()
	s, err := c.Sess.Session()
	if err != nil {
		return nil, err
	}
	defer s.EndSession(ctx)
	return s.StartTransaction(ctx, call)
}
